/*
 *    Copyright 2022 The DSMS Authors.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *        http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */

package com.dsms.modules.alert.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.dsms.common.constant.AlertRuleModuleEnum;
import com.dsms.common.constant.AlertRuleTypeEnum;
import com.dsms.common.constant.ResultCode;
import com.dsms.common.exception.DsmsEngineException;
import com.dsms.modules.alert.entity.AlertRule;
import com.dsms.modules.alert.mapper.AlertRuleMapper;
import com.dsms.modules.alert.model.vo.AlertRulePageVO;
import com.dsms.modules.alert.model.vo.AlertRuleQueryVO;
import com.dsms.modules.alert.model.vo.AlertRuleUpdateVO;
import com.dsms.modules.alert.service.IAlertRuleService;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;

import java.math.BigDecimal;
import java.util.Objects;


@Service
public class AlertRuleServiceImpl extends ServiceImpl<AlertRuleMapper, AlertRule> implements IAlertRuleService {


    /*
     * COMMON_RULE_MIN_VALUE and COMMON_RULE_MAX_VALUE are the thresholds of common indicators,
     * including cpu usage, memory usage, disk usage, OSDs outage rate, and storage pool usage,
     * they're both percentages
     * */
    private static final int COMMON_RULE_MIN_VALUE = 0;
    private static final int COMMON_RULE_MAX_VALUE = 100;
    //COMMON_RULE_MAX_DECIMAL is used to limit the threshold to 2 decimal digits
    private static final int COMMON_RULE_MAX_DECIMAL = 2;
    private static final String COMMON_RULE = "%s阈值范围为%s～%s,且不可超过%s位小数";

    /*
     * NETWORK_RULE_MIN_VALUE and NETWORK_RULE_MAX_VALUE are the thresholds of network indicators,
     * including NIC packet loss rate and NIC error rate
     * */
    private static final double NETWORK_RULE_MIN_VALUE = 0;
    private static final double NETWORK_RULE_MAX_VALUE = 1;
    //NETWORK_RULE_MAX_DECIMAL is used to limit the threshold to 4 decimal digits
    private static final int NETWORK_RULE_MAX_DECIMAL = 4;
    private static final String NETWORK_RULE = "%s阈值范围为%s～%s,且不可超过%s位小数";

    /*
     * MON_RULE_MIN_VALUE and MON_RULE_MAX_VALUE are the thresholds of mon's number,
     * in general, there is no explicit limit to the maximum number of mon nodes
     * */
    private static final int MON_RULE_MIN_VALUE = 0;
    private static final int MON_RULE_MAX_VALUE = Integer.MAX_VALUE;
    private static final String MON_RULE = "mon节点数阈值范围为%s～%s的整数";

    /*
     * PGS_RULE_MIN_VALUE and PGS_RULE_MAX_VALUE are the thresholds of inactive pg's number,
     * in general, there is no explicit limit to the maximum number of inactive pg
     * */
    private static final int PGS_RULE_MIN_VALUE = 0;
    private static final int PGS_RULE_MAX_VALUE = Integer.MAX_VALUE;
    private static final String PGS_RULE = "pgs不活跃数阈值范围为%s～%s的整数";

    //RULE_ZERO_DECIMAL is used to limit that the threshold must be an integer
    private static final int RULE_ZERO_DECIMAL = 0;

    @Override
    public Page<AlertRule> getAlertRulePage(AlertRulePageVO alertRulePageVO) {
        Page<AlertRule> alertRulePage = new Page<>(alertRulePageVO.getPageNo(), alertRulePageVO.getPageSize());

        if (ObjectUtils.isEmpty(alertRulePageVO.getRuleLevel())) {
            alertRulePage = this.page(alertRulePage);
        } else {
            LambdaQueryWrapper<AlertRule> queryWrapper = new LambdaQueryWrapper<AlertRule>().eq(AlertRule::getRuleLevel, alertRulePageVO.getRuleLevel());
            alertRulePage = this.page(alertRulePage, queryWrapper);
        }
        alertRulePage.getRecords().forEach(alertRule -> {
            alertRule.setRuleName(AlertRuleTypeEnum.getalertRuleTypeEnum(alertRule.getRuleMetric()).getName());
            alertRule.setRuleModule(AlertRuleModuleEnum.getAlertRuleModuleEnum(alertRule.getRuleModule()).getModuleName());
        });

        return alertRulePage;
    }

    @Override
    public Boolean updateAlertRule(AlertRuleUpdateVO alertRuleUpdateVO) {
        //validate if the field of alertRuleUpdateVO is legal
        validateAlertRuleField(alertRuleUpdateVO);

        AlertRule alertRule = convertToAlertRule(alertRuleUpdateVO);

        return updateById(alertRule);
    }

    private void validateAlertRuleField(AlertRuleUpdateVO alertRuleUpdateVO) {
        //parse the value of ruleThreshold and the length of decimal
        double ruleThreshold = Double.parseDouble(alertRuleUpdateVO.getRuleThreshold());
        int decimalLength = new BigDecimal(alertRuleUpdateVO.getRuleThreshold()).scale();

        //get the alertRule's type
        AlertRule alertRule = this.getById(alertRuleUpdateVO.getRuleId());
        AlertRuleTypeEnum ruleType = AlertRuleTypeEnum.getalertRuleTypeEnum(alertRule.getRuleMetric());

        boolean isLegal;
        assert ruleType != null;
        String typeName = ruleType.getName();
        String message;

        //validate ruleThreshold according to rule type
        switch (Objects.requireNonNull(ruleType)) {
            case CPU_USAGE:
            case OSD_DOWN:
            case MEMORY_USAGE:
            case DEVICE_USAGE:
            case POOL_CAPACITY:
                isLegal = ruleThreshold >= COMMON_RULE_MIN_VALUE && ruleThreshold <= COMMON_RULE_MAX_VALUE && decimalLength <= COMMON_RULE_MAX_DECIMAL;
                message = String.format(COMMON_RULE, typeName, COMMON_RULE_MIN_VALUE, COMMON_RULE_MAX_VALUE, COMMON_RULE_MAX_DECIMAL);
                break;
            case NETWORK_DROP:
            case NETWORK_ERROR:
                isLegal = ruleThreshold >= NETWORK_RULE_MIN_VALUE && ruleThreshold <= NETWORK_RULE_MAX_VALUE && decimalLength <= NETWORK_RULE_MAX_DECIMAL;
                message = String.format(NETWORK_RULE, typeName, NETWORK_RULE_MIN_VALUE, NETWORK_RULE_MAX_VALUE, NETWORK_RULE_MAX_DECIMAL);
                break;
            case MON_COUNT:
                isLegal = ruleThreshold >= MON_RULE_MIN_VALUE && ruleThreshold <= MON_RULE_MAX_VALUE && decimalLength == RULE_ZERO_DECIMAL;
                message = String.format(MON_RULE, MON_RULE_MIN_VALUE, MON_RULE_MAX_VALUE);
                break;
            case PG_INACTIVE:
                isLegal = ruleThreshold >= PGS_RULE_MIN_VALUE && ruleThreshold <= PGS_RULE_MAX_VALUE && decimalLength == RULE_ZERO_DECIMAL;
                message = String.format(PGS_RULE, PGS_RULE_MIN_VALUE, PGS_RULE_MAX_VALUE);
                break;
            case CEPH_HEALTH_WARN:
            case CEPH_HEALTH_ERROR:
            case OSD_SLOW:
                LambdaQueryWrapper<AlertRule> alertRuleLambdaQueryWrapper = new LambdaQueryWrapper<>();
                alertRuleLambdaQueryWrapper.eq(AlertRule::getRuleMetric, ruleType.getMetric());
                AlertRule rule = this.getOne(alertRuleLambdaQueryWrapper);
                isLegal = Objects.equals(ruleThreshold, Double.valueOf(rule.getRuleThreshold()));
                message = "不可更改阈值";
                break;
            default:
                isLegal = false;
                message = "告警规则类型不存在";
        }
        if (!isLegal) {
            throw DsmsEngineException.exceptionWithMessage(message, ResultCode.ALERT_RULE_FIELD_ERROR);
        }
    }

    @Override
    public Boolean resetAlertRule(AlertRuleQueryVO alertRuleQueryVO) {
        AlertRule currentAlertRule = getById(alertRuleQueryVO.getRuleId());

        currentAlertRule.setRuleThreshold(currentAlertRule.getRuleThresholdInit());
        currentAlertRule.setRuleLevel(currentAlertRule.getRuleLevelInit());
        currentAlertRule.setRuleTimes(currentAlertRule.getRuleTimesInit());

        return updateById(currentAlertRule);
    }


    public AlertRule convertToAlertRule(AlertRuleUpdateVO alertRuleUpdateVO) {
        if (alertRuleUpdateVO == null) {
            return null;
        }

        AlertRule alertRule = new AlertRule();

        alertRule.setId(alertRuleUpdateVO.getRuleId());
        alertRule.setRuleThreshold(alertRuleUpdateVO.getRuleThreshold());
        alertRule.setRuleTimes(alertRuleUpdateVO.getRuleTimes());
        alertRule.setRuleLevel(alertRuleUpdateVO.getRuleLevel());

        return alertRule;
    }

}
